Skip to content

Cherry pick safe commits#141

Merged
omeritzics merged 9 commits intomainfrom
cherry-pick-safe-commits
Feb 14, 2026
Merged

Cherry pick safe commits#141
omeritzics merged 9 commits intomainfrom
cherry-pick-safe-commits

Conversation

@omeritzics
Copy link
Owner

@omeritzics omeritzics commented Feb 14, 2026

PR Type

Enhancement, Documentation


Description

  • Introduced comprehensive expressive design system with new animated components (ExpressiveButton, ExpressiveCard, ExpressiveListTile, ExpressiveChip, ExpressiveSurface, ExpressiveContainer, ExpressiveBadge, ExpressiveRefreshIndicator)

  • Created AnimatedNavigationBar component with smooth scale, slide, and fade animations

  • Added CustomAppBar reusable component to replace hardcoded SliverAppBar implementations across multiple pages

  • Implemented ExpressiveMotion utility class with Material Design 3 motion durations, curves, and transition helpers

  • Refactored UI pages (apps.dart, settings.dart, add_app.dart, import_export.dart) to use new expressive components and custom app bar

  • Updated all 32 translation files to clarify useMaterialYou setting as "Use Material You colors" and soften googleVerificationWarningP1 language

  • Simplified error message handling in apps_provider.dart by removing categorization logic

  • Removed Flutter code analysis step from CI workflow

  • Removed unused animations package import and replaced ExpressiveMotion constants with inline duration values in some components


Diagram Walkthrough

flowchart LR
  A["New Expressive<br/>Components"] --> B["Updated Pages<br/>apps, settings, etc."]
  C["CustomAppBar<br/>Component"] --> B
  D["AnimatedNavigationBar"] --> E["Home Page<br/>Navigation"]
  F["ExpressiveMotion<br/>Utilities"] --> A
  G["Translation<br/>Updates"] --> H["32 Language<br/>Files"]
  B --> I["Enhanced UI<br/>with Animations"]
  E --> I
Loading

File Walkthrough

Relevant files
Enhancement
12 files
apps.dart
Refactor UI components with expressive design system         

lib/pages/apps.dart

  • Replaced hardcoded responsive sizing with fixed pixel values for
    better consistency
  • Refactored app list item UI from InkWell wrapper to native ListTile
    with ExpressiveCard
  • Replaced Card.outlined grid items with new ExpressiveCard component
    featuring animations
  • Replaced SliverAppBar with reusable CustomAppBar component
  • Replaced RefreshIndicator with ExpressiveRefreshIndicator for enhanced
    animations
  • Updated navigation to use MaterialPageRoute instead of
    PageRouteBuilder with SharedAxisTransition
+148/-191
expressive_buttons.dart
New expressive button components with animations                 

lib/components/expressive_buttons.dart

  • Created new ExpressiveButton with smooth animations, ripple effects,
    and elevation changes
  • Implemented ExpressiveIconButton with scale and rotation animations on
    interaction
  • Added ExpressiveFilledButton with gradient backgrounds and color
    animations
  • All buttons support customizable styling, animation duration, and
    disabled states
+520/-0 
expressive_components.dart
New expressive card, list tile, and chip components           

lib/components/expressive_components.dart

  • Created ExpressiveCard with scale and elevation animations on
    tap/hover
  • Implemented ExpressiveListTile wrapping ListTile with animation
    support
  • Added ExpressiveChip with elastic scale animation on appearance
  • All components feature Material Design 3 styling and smooth
    interactions
+386/-0 
expressive_surfaces.dart
New expressive surface and container components                   

lib/components/expressive_surfaces.dart

  • Created ExpressiveSurface with gradient backgrounds and shadow effects
  • Implemented ExpressiveContainer with scale and opacity animations
  • Added ExpressiveBadge with elastic scale and rotation animations
  • Components support customizable colors, gradients, and elevation
+348/-0 
import_export.dart
Migrate import/export page to expressive buttons                 

lib/pages/import_export.dart

  • Replaced FilledButton.icon with ExpressiveFilledButton throughout the
    page
  • Replaced SliverAppBar with CustomAppBar component
  • Replaced TextButton.icon with ExpressiveButton in dialogs
  • Updated button styling to use new expressive components
+30/-56 
expressive_motion.dart
New expressive motion and transition utilities                     

lib/utils/expressive_motion.dart

  • Created utility class with Material Design 3 motion durations and
    curves
  • Implemented transition helpers: fadeScaleTransition,
    slideFadeTransition, sharedAxisTransition
  • Added ExpressivePageRoute for custom page transitions with slide and
    fade effects
  • Provided helper methods for button, card, list item, and dialog
    animations
+289/-0 
settings.dart
Refactor settings page with fixed sizing and custom app bar

lib/pages/settings.dart

  • Replaced hardcoded responsive sizing with fixed pixel values in color
    picker
  • Simplified useMaterialThemeSwitch UI by removing Card.outlined wrapper
  • Replaced SliverAppBar with CustomAppBar component
  • Updated color picker to use ColorIndicator widget
+34/-87 
animated_navigation_bar.dart
New animated navigation bar component                                       

lib/components/animated_navigation_bar.dart

  • Created new AnimatedNavigationBar with smooth scale, slide, and fade
    animations
  • Implemented per-destination animation logic with haptic feedback
  • Added visual indicators for selected state with animated dot
  • Features Material Design 3 styling with secondary container colors
+208/-0 
expressive_refresh_indicator.dart
New expressive refresh indicator component                             

lib/components/expressive_refresh_indicator.dart

  • Created ExpressiveRefreshIndicator wrapping Flutter's RefreshIndicator
  • Implemented rotation, scale, and opacity animations during refresh
  • Supports customizable displacement, color, and animation duration
  • Manages refresh state with animation controller lifecycle
+96/-0   
add_app.dart
Simplify add app page with custom app bar                               

lib/pages/add_app.dart

  • Removed import of animations package
  • Replaced SliverAppBar with CustomAppBar component
  • Removed unused app_button import
+2/-36   
home.dart
Add import/export page to navigation                                         

lib/pages/home.dart

  • Added AnimatedNavigationBar import
  • Added new ImportExportPage navigation item to pages list
  • Replaced NavigationBar with AnimatedNavigationBar component
+7/-1     
custom_app_bar.dart
New custom app bar component                                                         

lib/components/custom_app_bar.dart

  • Created new CustomAppBar stateful widget as reusable app bar component
  • Implements SliverAppBar with pinned behavior and flexible space
  • Supports customizable title with consistent styling across pages
+30/-0   
Refactoring
3 files
cached_app_icon.dart
Remove expressive motion utility dependency                           

lib/components/cached_app_icon.dart

  • Removed import of ExpressiveMotion utility class
  • Replaced ExpressiveMotion constants with inline duration and curve
    values
  • Updated animation durations to use Duration directly instead of
    utility constants
+6/-7     
app_button.dart
Remove expressive motion utility dependency                           

lib/components/app_button.dart

  • Removed import of ExpressiveMotion utility class
  • Replaced ExpressiveMotion constants with inline duration and curve
    values
  • Updated animation durations to use Duration directly
+5/-6     
apps_provider.dart
Simplify error message handling in import                               

lib/providers/apps_provider.dart

  • Simplified error message mapping by removing categorization logic
  • Changed from user-friendly error messages to raw error string output
  • Removed detailed error logging and conditional message handling
+1/-23   
Documentation
31 files
he.json
Add and update Hebrew translations                                             

assets/translations/he.json

  • Translated multiple English strings to Hebrew
  • Updated removeOutdatedFilter, showWebInAppView,
    appWithIdOrNameNotFound translations
  • Added Hebrew translations for markInstalled, markUpdated, and other UI
    strings
  • Added plural form translations for various keys
+31/-16 
ar.json
Update Arabic translations                                                             

assets/translations/ar.json

  • Updated useMaterialYou translation to clarify "colors"
  • Modified googleVerificationWarningP1 to soften language about Updatium
    functionality
  • Added plural form translations for multiple keys
+18/-2   
hu.json
Update Hungarian translations                                                       

assets/translations/hu.json

  • Updated useMaterialYou translation to clarify "colors"
  • Modified googleVerificationWarningP1 to soften language about Updatium
    functionality
+2/-2     
uk.json
Update Ukrainian translations                                                       

assets/translations/uk.json

  • Updated useMaterialYou translation to clarify "colors"
  • Modified googleVerificationWarningP1 to soften language about Updatium
    functionality
+2/-2     
cs.json
Update Czech translations                                                               

assets/translations/cs.json

  • Updated useMaterialYou translation to clarify "colors"
  • Modified googleVerificationWarningP1 to soften language about Updatium
    functionality
+2/-2     
ja.json
Update Japanese translations                                                         

assets/translations/ja.json

  • Updated useMaterialYou translation to clarify "colors"
  • Modified googleVerificationWarningP1 to soften language about Updatium
    functionality
+2/-2     
pl.json
Polish translation updates for Material You and Google verification
warning

assets/translations/pl.json

  • Updated useMaterialYou translation from "Material You" to "Użyj
    kolorów Material You" (Use Material You colors)
  • Modified googleVerificationWarningP1 to soften language about
    Updatium's future functionality on certified Android devices
+2/-2     
et.json
Estonian translation updates for Material You and Google verification

assets/translations/et.json

  • Updated useMaterialYou translation to include "värve" (colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
en.json
English translation updates and error message removal       

assets/translations/en.json

  • Updated useMaterialYou from "Use Material You" to "Use Material You
    colors"
  • Removed four error message keys: networkError, invalidUrlFormat,
    accessDenied, importFailed
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-6     
zh-Hant-TW.json
Traditional Chinese translation updates for Material You 

assets/translations/zh-Hant-TW.json

  • Updated useMaterialYou to include "顏色" (colors)
  • Modified googleVerificationWarningP1 to soften language about app
    functionality
+2/-2     
tr.json
Turkish translation updates for Material You and Google verification

assets/translations/tr.json

  • Updated useMaterialYou to "Material You renklerini kullan" (Use
    Material You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
vi.json
Vietnamese translation updates for Material You                   

assets/translations/vi.json

  • Updated useMaterialYou to "Sử dụng màu Material You" (Use Material You
    colors)
  • Modified googleVerificationWarningP1 with improved translation and
    softer language
+2/-2     
ml.json
Malayalam translation updates for Material You                     

assets/translations/ml.json

  • Updated useMaterialYou to "Material You നിറങ്ങൾ ഉപയോഗിക്കുക" (Use
    Material You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
da.json
Danish translation updates for Material You                           

assets/translations/da.json

  • Updated useMaterialYou to "Brug Material You-farver" (Use Material You
    colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
ca.json
Catalan translation updates for Material You                         

assets/translations/ca.json

  • Updated useMaterialYou to "Usar colors Material You" (Use Material You
    colors)
  • Modified googleVerificationWarningP1 with improved translation and
    softer language
+2/-2     
it.json
Italian translation updates for Material You                         

assets/translations/it.json

  • Updated useMaterialYou to "Utilizzate i colori Material You" (Use
    Material You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
fa.json
Persian translation updates for Material You                         

assets/translations/fa.json

  • Updated useMaterialYou to "استفاده از رنگ‌های Material You" (Use
    Material You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
sv.json
Swedish translation updates for Material You                         

assets/translations/sv.json

  • Updated useMaterialYou to "Använd Material You-färger" (Use Material
    You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
bs.json
Bosnian translation updates for Material You                         

assets/translations/bs.json

  • Updated useMaterialYou to "Koristi Material You boje" (Use Material
    You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
ru.json
Russian translation updates for Material You                         

assets/translations/ru.json

  • Updated useMaterialYou to "Использовать цвета Material You" (Use
    Material You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
fr.json
French translation updates for Material You                           

assets/translations/fr.json

  • Updated useMaterialYou to "Utiliser les couleurs Material You" (Use
    Material You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
gl.json
Galician translation updates for Material You                       

assets/translations/gl.json

  • Updated useMaterialYou to "Usar cores Material You" (Use Material You
    colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
de.json
German translation updates for Material You                           

assets/translations/de.json

  • Updated useMaterialYou to "Material You Farben verwenden" (Use
    Material You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
id.json
Indonesian translation updates for Material You                   

assets/translations/id.json

  • Updated useMaterialYou to "Gunakan warna Material You" (Use Material
    You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
nl.json
Dutch translation updates for Material You                             

assets/translations/nl.json

  • Updated useMaterialYou to "Material You kleuren gebruiken" (Use
    Material You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
pt-BR.json
Brazilian Portuguese translation updates for Material You

assets/translations/pt-BR.json

  • Updated useMaterialYou to "Usar cores Material You" (Use Material You
    colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
es.json
Spanish translation updates for Material You                         

assets/translations/es.json

  • Updated useMaterialYou to "Aplicar colores Material You" (Apply
    Material You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
pt.json
Portuguese translation updates for Material You                   

assets/translations/pt.json

  • Updated useMaterialYou to "Utilizar cores Material You" (Use Material
    You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
ko.json
Korean translation updates for Material You                           

assets/translations/ko.json

  • Updated useMaterialYou to "Material You 색상 사용" (Use Material You
    colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
en-EO.json
Esperanto translation updates for Material You                     

assets/translations/en-EO.json

  • Updated useMaterialYou to "Uzi Material You kolorojn" (Use Material
    You colors)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
zh.json
Simplified Chinese translation updates for Material You   

assets/translations/zh.json

  • Updated useMaterialYou to "使用 Material You 配色" (Use Material You color
    scheme)
  • Modified googleVerificationWarningP1 to use softer language about app
    functionality
+2/-2     
Configuration changes
1 files
ci.yml
Remove Flutter code analysis from CI workflow                       

.github/workflows/ci.yml

  • Removed the "Analyze Code" job step that ran flutter analyze
  • Adjusted whitespace formatting in the workflow file
+1/-5     

omeritzics and others added 8 commits February 11, 2026 20:34
(cherry picked from commit 96d6aea)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@qodo-code-review
Copy link

qodo-code-review bot commented Feb 14, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Sensitive error disclosure

Description: Import error handling now returns raw errorsMap[e].toString() values to the UI, which can
expose sensitive internal details (e.g., stack traces, filesystem paths, request/URL
contents, or exception messages containing tokens) to end users and logs/screenshots.
apps_provider.dart [2409-2412]

Referred Code
List<List<String>> errors = errorsMap.keys
    .map((e) => [e, errorsMap[e].toString()])
    .toList();
return errors;
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Raw errors shown: The dialog renders raw error strings (e[1]) directly to the user which may expose
internal/system details depending on upstream error content.

Referred Code
const SizedBox(height: 16),
Text(
  tr('followingURLsHadErrors'),
  style: Theme.of(context).textTheme.bodyLarge,
),
...widget.errors.map((e) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: [
      const SizedBox(height: 16),
      Text(e[0]),
      Text(e[1], style: const TextStyle(fontStyle: FontStyle.italic)),
    ],

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unvalidated link launch: Markdown link taps launch externally constructed URLs from href (potentially untrusted
changelog content) without explicit allowlisting/validation beyond http(s)/relative
handling.

Referred Code
onTapLink: (text, href, title) {
  if (href != null) {
    launchUrlString(
      href.startsWith('http://') ||
              href.startsWith('https://')
          ? href
          : '${Uri.parse(app.url).origin}/$href',
      mode: LaunchMode.externalApplication,
    );

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@gemini-code-assist
Copy link

Summary of Changes

Hello @omeritzics, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's user experience by modernizing its UI with Material Design 3 principles and introducing expressive animations. It involves adding a comprehensive set of new animated UI components, extensively updating translation files for improved clarity and pluralization, and refactoring existing UI elements to seamlessly integrate these visual and interactive improvements. The changes aim to create a more dynamic, visually engaging, and globally accessible application.

Highlights

  • Internationalization Updates: Updated numerous translation files across various languages to refine existing strings, particularly clarifying 'useMaterialYou' to 'Use Material You colors' and softening the 'googleVerificationWarningP1' message from 'will no longer work' to 'may stop working'. The English translation file also saw the removal of several specific error messages and the addition of pluralization support for certain keys, while the Hebrew file received several clarity improvements.
  • New Expressive UI Components: Introduced a suite of new Flutter widgets designed for modern Material Design 3 interactions, including an AnimatedNavigationBar, a CustomAppBar, and a collection of ExpressiveButton variants (standard, icon, and filled). Additionally, new expressive components for ExpressiveCard, ExpressiveListTile, ExpressiveChip, ExpressiveRefreshIndicator, ExpressiveSurface, ExpressiveContainer, and ExpressiveBadge were added to enhance visual appeal and user feedback.
  • UI Refactoring and Modernization: Refactored existing UI elements across the application to integrate the newly introduced expressive components and motion utilities. This involved replacing standard SliverAppBar and RefreshIndicator widgets with their custom expressive counterparts, updating button implementations, and adjusting various layout and styling properties (like icon sizes and text styles) to align with the new design language. The ImportExportPage was also integrated into the main navigation.
  • Animation System Streamlining: Centralized animation logic by introducing a new expressive_motion.dart utility file. This change led to the removal of direct expressive_motion.dart imports from app_button.dart and cached_app_icon.dart, with their animation curves and durations now being explicitly defined or managed by the new expressive components.
  • Simplified Error Handling in Apps Provider: Simplified the error mapping logic within apps_provider.dart for import errors, transitioning from specific user-friendly messages for various error types to more generic error strings.
Changelog
  • assets/translations/ar.json
    • Updated 'useMaterialYou' translation.
    • Added pluralization support for several keys.
  • assets/translations/bs.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/ca.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/cs.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/da.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/de.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/en-EO.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/en.json
    • Updated 'useMaterialYou' translation.
    • Removed 'networkError', 'invalidUrlFormat', 'accessDenied', and 'importFailed' keys.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/es.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/et.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/fa.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/fr.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/gl.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/he.json
    • Updated 'removeOutdatedFilter' translation.
    • Updated 'showWebInAppView' translation.
    • Updated 'appWithIdOrNameNotFound' translation.
    • Updated 'markInstalled' translation.
    • Updated 'markUpdated' translation.
    • Updated 'releaseDateAsVersion' translation.
    • Updated 'releaseTitleAsVersion' translation.
    • Updated 'reversePageTransitions' translation.
    • Updated 'appsPossiblyUpdated' translation.
    • Updated 'xWasPossiblyUpdatedToY' translation.
    • Updated 'verifyLatestTag' translation.
    • Updated 'reverseSort' translation.
    • Updated 'bgTaskStarted' translation.
    • Updated 'skipUpdateNotifications' translation.
    • Added pluralization support for several keys.
  • assets/translations/hu.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/id.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/it.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/ja.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/ko.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/ml.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/nl.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/pl.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/pt-BR.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/pt.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/ru.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/sv.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/tr.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/uk.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/vi.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/zh-Hant-TW.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • assets/translations/zh.json
    • Updated 'useMaterialYou' translation.
    • Updated 'googleVerificationWarningP1' message.
  • lib/components/animated_navigation_bar.dart
    • Added a new animated navigation bar component.
  • lib/components/app_button.dart
    • Removed import for 'expressive_motion.dart'.
    • Updated animation durations and curves to fixed values.
  • lib/components/cached_app_icon.dart
    • Removed import for 'expressive_motion.dart'.
    • Updated animation durations and curves to fixed values.
  • lib/components/custom_app_bar.dart
    • Added a new custom app bar component.
  • lib/components/expressive_buttons.dart
    • Added new expressive button components: ExpressiveButton, ExpressiveIconButton, and ExpressiveFilledButton.
  • lib/components/expressive_components.dart
    • Added new expressive UI components: ExpressiveCard, ExpressiveListTile, and ExpressiveChip.
  • lib/components/expressive_refresh_indicator.dart
    • Added a new expressive refresh indicator component.
  • lib/components/expressive_surfaces.dart
    • Added new expressive UI components: ExpressiveSurface, ExpressiveContainer, and ExpressiveBadge.
  • lib/pages/add_app.dart
    • Removed import for 'animations/animations.dart'.
    • Replaced SliverAppBar with CustomAppBar.
    • Removed the FloatingActionButton.extended for import/export.
  • lib/pages/apps.dart
    • Added imports for 'animations/animations.dart', 'expressive_refresh_indicator.dart', 'custom_app_bar.dart', and 'expressive_components.dart'.
    • Replaced RefreshIndicator with ExpressiveRefreshIndicator.
    • Replaced SliverAppBar with CustomAppBar.
    • Adjusted icon sizes and text styles for various widgets.
    • Replaced Card.outlined with ExpressiveCard for app listings.
    • Updated ListTile styling and behavior for app items.
  • lib/pages/home.dart
    • Added import for 'animated_navigation_bar.dart'.
    • Added 'ImportExportPage' to the navigation items.
    • Replaced NavigationBar with AnimatedNavigationBar.
  • lib/pages/import_export.dart
    • Added imports for 'expressive_buttons.dart' and 'custom_app_bar.dart'.
    • Replaced SliverAppBar with CustomAppBar.
    • Replaced FilledButton.icon with ExpressiveFilledButton.
    • Adjusted text styles for disclaimers and error messages.
    • Replaced TextButton.icon with ExpressiveButton in dialogs.
  • lib/pages/settings.dart
    • Added import for 'custom_app_bar.dart'.
    • Adjusted ColorPicker widget sizes.
    • Replaced ListTile with ColorIndicator for theme color selection.
    • Replaced Card.outlined with a Row for the Material You switch.
    • Replaced SliverAppBar with CustomAppBar.
  • lib/providers/apps_provider.dart
    • Simplified error mapping for import errors, returning generic error strings instead of specific user-friendly messages.
  • lib/utils/expressive_motion.dart
    • Added a new utility file for Material Design Expressive motion and transitions, including various animation durations, curves, and transition widgets.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/ci.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qodo-code-review
Copy link

qodo-code-review bot commented Feb 14, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Restore the removed static analysis step

Restore the flutter analyze step in the CI workflow to maintain code quality and
catch potential issues early.

.github/workflows/ci.yml [48-50]

        sed -i 's/signingConfig = signingConfigs.getByName("release")//g' android/app/build.gradle.kts || true
+
+  - name: Analyze Code
+    run: |2
+      flutter analyze
       
   - name: Build APK (Debug with ABI Splitting)

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies the removal of the flutter analyze step, which is critical for maintaining code quality and preventing bugs, and rightly recommends its restoration.

High
Fix broken URL spacing

Fix a broken URL in the Polish translation file by removing spaces around the
slashes.

assets/translations/pl.json [345]

-"googleVerificationWarningP2": "Więcej informacji można uzyskać na stronie https: / / keepandroidopen.org.",
+"googleVerificationWarningP2": "Więcej informacji można uzyskać na stronie https://keepandroidopen.org/.",
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly identifies and fixes a broken URL in a user-facing translation file, which improves the user experience by making the link functional.

Medium
Correct URL formatting

Fix a broken URL in the Czech translation file by removing spaces around the
slashes.

assets/translations/cs.json [345]

-"googleVerificationWarningP2": "Další informace získáte na adrese https: / / keepandroidopen.org.",
+"googleVerificationWarningP2": "Další informace získáte na adrese https://keepandroidopen.org/.",
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly identifies and fixes a broken URL in a user-facing translation file, which improves the user experience by making the link functional.

Medium
General
Remove unused animations

Remove the unused animation controllers and related fields from
_ExpressiveRefreshIndicatorState as they are initialized but never used in the
build method.

lib/components/expressive_refresh_indicator.dart [27-54]

-class _ExpressiveRefreshIndicatorState extends State<ExpressiveRefreshIndicator>
-    with TickerProviderStateMixin {
-  late AnimationController _animationController;
-  late Animation<double> _rotationAnimation;
-  late Animation<double> _scaleAnimation;
-  late Animation<double> _opacityAnimation;
-  late bool _isRefreshing = false;
-  ...
+class _ExpressiveRefreshIndicatorState extends State<ExpressiveRefreshIndicator> {
+  Future<void> _handleRefresh() async {
+    if (widget.onRefresh != null && widget.enabled) {
+      await widget.onRefresh!();
+    }
+  }
+
   @override
-  void initState() {
-    super.initState();
-    _animationController = AnimationController(
-      duration: widget.duration,
-      vsync: this,
-    );
-    _rotationAnimation = Tween<double>(begin: 0.0, end: 0.5).animate(
-      CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
-    );
-    _scaleAnimation = Tween<double>(begin: 1.0, end: 0.8).animate(
-      CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
-    );
-    _opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
-      CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
+  Widget build(BuildContext context) {
+    final colorScheme = Theme.of(context).colorScheme;
+    return RefreshIndicator(
+      displacement: widget.displacement,
+      color: widget.color ?? colorScheme.primary,
+      backgroundColor: colorScheme.surface,
+      strokeWidth: 2.5,
+      onRefresh: _handleRefresh,
+      child: widget.child,
     );
   }
-  // but these animations are never used in build()
-  ...
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the animations defined in the state are never used, and the proposed fix correctly removes the dead code, simplifying the widget.

Medium
Await animation completion before callback

Make the _handleTapUp method async and await the animation controller's reverse
animation before calling the onPressed callback to prevent the animation from
being interrupted.

lib/components/expressive_buttons.dart [118-123]

-void _handleTapUp(TapUpDetails details) {
+void _handleTapUp(TapUpDetails details) async {
   if (widget.enableAnimation) {
-    _controller.reverse();
+    await _controller.reverse();
   }
-  widget.onPressed?.call();
+  // Check if the widget is still mounted before calling the callback,
+  // as the user might have navigated away during the animation.
+  if (mounted) {
+    widget.onPressed?.call();
+  }
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a UX issue where animations are cut short on navigation and provides a valid fix, enhancing the component's intended behavior.

Medium
Await animation before calling callback

Make the _handleTapUp method async and await the animation controller's reverse
animation before calling the onTap callback to prevent the animation from being
interrupted.

lib/components/expressive_components.dart [102-108]

-void _handleTapUp(TapUpDetails details) {
+void _handleTapUp(TapUpDetails details) async {
   if (widget.enableAnimation) {
     setState(() => _isPressed = false);
-    _animationController.reverse();
+    await _animationController.reverse();
   }
-  widget.onTap?.call();
+  if (mounted) {
+    widget.onTap?.call();
+  }
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a UX issue where animations are cut short on navigation and provides a valid fix, enhancing the component's intended behavior.

Medium
  • Update

@omeritzics omeritzics merged commit c4939bf into main Feb 14, 2026
3 of 6 checks passed
@github-actions
Copy link

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a suite of new 'Expressive' UI components, including AnimatedNavigationBar, CustomAppBar, ExpressiveButton, ExpressiveIconButton, ExpressiveFilledButton, ExpressiveCard, ExpressiveListTile, ExpressiveChip, and ExpressiveRefreshIndicator, aiming to enhance the application's visual appeal and interactivity with Material Design 3 animations. The ExpressiveMotion utility class was added to centralize animation durations and curves, but its usage is inconsistent as app_button.dart and cached_app_icon.dart were simultaneously refactored to use hardcoded animation values instead of this new utility. The AddAppPage and ImportExportPage now utilize the new CustomAppBar, and the ImportExportPage also replaces standard FilledButton widgets with the new ExpressiveFilledButton. The AppsPage was updated to use ExpressiveRefreshIndicator and ExpressiveCard for its list items, along with fixed sizes for icons and spacing instead of MediaQuery based dynamic sizing. Additionally, the ImportExportPage was moved from a FloatingActionButton on the AddAppPage to a dedicated tab in the main navigation. Numerous translation files were updated to include a 'two' plural form for various strings and to refine existing translations, such as changing 'Use Material You' to 'Use Material You colors' and softening a Google verification warning. A critical review comment highlights a potential security vulnerability in the AppsProvider.import function, which lacks validation for imported JSON payloads, allowing arbitrary SharedPreferences key overwrites. Another significant concern is the removal of categorized error handling in AppsProvider.import, leading to raw error messages being displayed, which can leak sensitive information and degrade user experience. Further feedback points out unused properties in ExpressiveSurface, inefficient animation handling in AnimatedNavigationBar, and code duplication in ExpressiveChip's build method.

Comment on lines +1 to +96
import 'package:flutter/material.dart';

/// Material Design Expressive Refresh Indicator with smooth animations
class ExpressiveRefreshIndicator extends StatefulWidget {
final Widget child;
final Future<void> Function()? onRefresh;
final Color? color;
final double displacement;
final bool enabled;
final Duration duration;

const ExpressiveRefreshIndicator({
super.key,
required this.child,
this.onRefresh,
this.color,
this.displacement = 40.0,
this.enabled = true,
this.duration = const Duration(milliseconds: 300),
});

@override
State<ExpressiveRefreshIndicator> createState() =>
_ExpressiveRefreshIndicatorState();
}

class _ExpressiveRefreshIndicatorState extends State<ExpressiveRefreshIndicator>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _rotationAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;
late bool _isRefreshing = false;

@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: widget.duration,
vsync: this,
);

_rotationAnimation = Tween<double>(begin: 0.0, end: 0.5).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);

_scaleAnimation = Tween<double>(begin: 1.0, end: 0.8).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);

_opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
);
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}

Future<void> _handleRefresh() async {
if (widget.onRefresh != null && widget.enabled && !_isRefreshing) {
setState(() => _isRefreshing = true);

// Start animations
_animationController.repeat();

try {
await widget.onRefresh!();
} finally {
if (mounted) {
// Stop animations
_animationController.stop();
_animationController.reset();
setState(() => _isRefreshing = false);
}
}
}
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;

return RefreshIndicator(
displacement: widget.displacement,
color: widget.color ?? colorScheme.primary,
backgroundColor: colorScheme.surface,
strokeWidth: 2.5,
onRefresh: _handleRefresh,
child: widget.child,
);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This component is named ExpressiveRefreshIndicator and its state class defines several animations (_rotationAnimation, _scaleAnimation, _opacityAnimation). However, the build method only returns a standard RefreshIndicator and does not use these animations at all. This means the "expressive" part of the component is not implemented, and it behaves just like a regular RefreshIndicator. This should be fixed to implement the intended animations or the component should be removed to avoid confusion.

Comment on lines +58 to 62
tr('importExport'),
Icons.import_export,
const ImportExportPage(),
),
NavigationPageItem(tr('settings'), Icons.settings, const SettingsPage()),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The addition of the ImportExportPage to the main navigation exposes a critical vulnerability in the application's import logic. The AppsProvider.import function, which is the core of the import feature, accepts an arbitrary JSON payload and uses it to overwrite SharedPreferences keys without any validation or allow-listing. An attacker can provide a malicious JSON file (or trigger a deep link) that, when imported, modifies sensitive application settings, disables security features, or redirects the application's source URL. It is strongly recommended to implement a strict allow-list of safe settings that can be imported and to sanitize all input data.

return [e, userMessage];
}).toList();
List<List<String>> errors = errorsMap.keys
.map((e) => [e, errorsMap[e].toString()])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The error handling logic for importing apps now displays raw error messages directly in the UI, removing previous categorization. This is a security regression as it can leak sensitive information such as internal file paths, network details, or implementation specifics to the user. It also significantly degrades user experience by showing technical details instead of user-friendly messages like "Network connection failed" or "App not found". It is recommended to restore the error categorization logic or ensure that only safe, localized messages are displayed. This may require re-adding the removed translation keys (networkError, invalidUrlFormat, accessDenied, importFailed) to assets/translations/en.json and other language files.

Comment on lines +1 to +208
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

/// Animated Navigation Bar with expressive interactions and animations
class AnimatedNavigationBar extends StatefulWidget {
final List<NavigationDestination> destinations;
final int selectedIndex;
final ValueChanged<int>? onDestinationSelected;

const AnimatedNavigationBar({
super.key,
required this.destinations,
required this.selectedIndex,
this.onDestinationSelected,
});

@override
State<AnimatedNavigationBar> createState() => _AnimatedNavigationBarState();
}

class _AnimatedNavigationBarState extends State<AnimatedNavigationBar>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _slideAnimation;
late Animation<double> _fadeAnimation;
int? _previousIndex;

@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);

_slideAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic),
);

_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic),
);

_previousIndex = widget.selectedIndex;
_animationController.forward();
}

@override
void didUpdateWidget(AnimatedNavigationBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.selectedIndex != widget.selectedIndex) {
_previousIndex = oldWidget.selectedIndex;
_animationController.reset();
_animationController.forward();
}
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}

void _handleDestinationSelected(int index) {
HapticFeedback.selectionClick();
widget.onDestinationSelected?.call(index);
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;

return Container(
height: 80,
decoration: BoxDecoration(
color: colorScheme.surface,
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.12),
blurRadius: 12,
offset: const Offset(0, -2),
),
],
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: widget.destinations.asMap().entries.map((entry) {
final index = entry.key;
final destination = entry.value;
final isSelected = index == widget.selectedIndex;

return Expanded(
child: GestureDetector(
onTap: () => _handleDestinationSelected(index),
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
// Calculate animation progress
final bool isEntering =
isSelected && _previousIndex != index;
final bool isExiting =
!isSelected && _previousIndex == index;

double scale = 1.0;
double opacity = 1.0;
double verticalOffset = 0.0;

if (isEntering) {
scale = 0.8 + (0.2 * _slideAnimation.value);
opacity = _fadeAnimation.value;
verticalOffset = 4.0 * (1.0 - _slideAnimation.value);
} else if (isExiting) {
scale = 1.0 - (0.2 * _slideAnimation.value);
opacity = 1.0 - (0.3 * _fadeAnimation.value);
verticalOffset = -4.0 * _slideAnimation.value;
} else if (isSelected) {
scale = 1.0;
opacity = 1.0;
verticalOffset = 0.0;
} else {
scale = 1.0;
opacity = 0.7;
verticalOffset = 0.0;
}

return Transform.translate(
offset: Offset(0, verticalOffset),
child: Transform.scale(
scale: scale,
child: AnimatedOpacity(
opacity: opacity,
duration: const Duration(milliseconds: 200),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: isSelected
? colorScheme.secondaryContainer
: Colors.transparent,
border: Border.all(
color: isSelected
? colorScheme.secondary.withOpacity(0.3)
: Colors.transparent,
width: 1,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
transform: Matrix4.identity()
..scale(isSelected ? 1.1 : 1.0),
child: IconTheme(
data: IconThemeData(
color: isSelected
? colorScheme.onSecondaryContainer
: colorScheme.onSurface.withOpacity(
0.7,
),
size: 24,
),
child: destination.icon,
),
),
if (isSelected)
AnimatedContainer(
duration: const Duration(
milliseconds: 200,
),
curve: Curves.easeOutCubic,
transform: Matrix4.identity()
..scale(_slideAnimation.value),
child: Container(
width: 4,
height: 4,
decoration: BoxDecoration(
color: colorScheme.primary,
shape: BoxShape.circle,
),
),
),
],
),
),
),
),
);
},
),
),
);
}).toList(),
),
),
),
);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The animations for scale, opacity, and verticalOffset are calculated inside the AnimatedBuilder but then passed to Transform.scale, AnimatedOpacity, and Transform.translate respectively. While this works, it's slightly inefficient and less direct than it could be. AnimatedOpacity creates its own AnimationController. You can directly use the _fadeAnimation.value on an Opacity widget inside the AnimatedBuilder to avoid this. Similarly, the transforms can be combined into a single Transform widget with a Matrix4 for better performance.

Comment on lines +21 to +26
title: Text(
widget.title,
style: TextStyle(
color: Theme.of(context).textTheme.bodyMedium!.color,
),
),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The CustomAppBar title's TextStyle only sets the color, using Theme.of(context).textTheme.bodyMedium!.color. This is not ideal for a few reasons:

  1. It force-unwraps a nullable property (!), which can lead to runtime errors if bodyMedium or its color is null.
  2. bodyMedium is generally not the correct text style for an app bar title. It's better to use styles from appBarTheme or more appropriate textTheme styles like titleLarge.
  3. Only setting the color means other properties (like font size, weight) will fall back to the FlexibleSpaceBar's default, which might not be what you intend.
Suggested change
title: Text(
widget.title,
style: TextStyle(
color: Theme.of(context).textTheme.bodyMedium!.color,
),
),
style: Theme.of(context).appBarTheme.titleTextStyle ?? Theme.of(context).textTheme.titleLarge,

Comment on lines +294 to +384
return widget.enableAnimation
? AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Material(
color:
widget.backgroundColor ?? colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(8),
child: InkWell(
onTap: widget.onTap,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding:
widget.padding ??
const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.avatar != null) ...[
widget.avatar!,
const SizedBox(width: 8),
],
DefaultTextStyle(
style: TextStyle(
color:
widget.foregroundColor ??
colorScheme.onSecondaryContainer,
fontSize: 14,
fontWeight: FontWeight.w500,
),
child: widget.label,
),
if (widget.deleteIcon != null) ...[
const SizedBox(width: 8),
GestureDetector(
onTap: widget.onDeleted,
child: widget.deleteIcon!,
),
],
],
),
),
),
),
);
},
)
: Material(
color: widget.backgroundColor ?? colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(8),
child: InkWell(
onTap: widget.onTap,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding:
widget.padding ??
const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.avatar != null) ...[
widget.avatar!,
const SizedBox(width: 8),
],
DefaultTextStyle(
style: TextStyle(
color:
widget.foregroundColor ??
colorScheme.onSecondaryContainer,
fontSize: 14,
fontWeight: FontWeight.w500,
),
child: widget.label,
),
if (widget.deleteIcon != null) ...[
const SizedBox(width: 8),
GestureDetector(
onTap: widget.onDeleted,
child: widget.deleteIcon!,
),
],
],
),
),
),
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The build method for ExpressiveChip has two large, nearly identical branches for when enableAnimation is true or false. This code duplication makes the component harder to maintain. Consider refactoring this to extract the common UI logic into a separate build method or widget, and then conditionally wrap it with the AnimatedBuilder for the animation.

Example Refactor Structure:

Widget _buildChipContent(BuildContext context) {
  // ... build the Material, InkWell, Padding, Row, etc.
}

@override
Widget build(BuildContext context) {
  final chipContent = _buildChipContent(context);
  if (!widget.enableAnimation) {
    return chipContent;
  }
  return AnimatedBuilder(
    animation: _scaleAnimation,
    builder: (context, child) {
      return Transform.scale(
        scale: _scaleAnimation.value,
        child: child,
      );
    },
    child: chipContent,
  );
}

Comment on lines +1 to +65
import 'package:flutter/material.dart';

/// Expressive Surface - Modern surface treatments with Material You effects
class ExpressiveSurface extends StatelessWidget {
final Widget child;
final Color? color;
final double? elevation;
final BorderRadius? borderRadius;
final EdgeInsetsGeometry? padding;
final bool enableGradient;
final bool enableNoise;
final BlendMode? blendMode;

const ExpressiveSurface({
super.key,
required this.child,
this.color,
this.elevation,
this.borderRadius,
this.padding,
this.enableGradient = true,
this.enableNoise = false,
this.blendMode,
});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final surfaceColor = color ?? colorScheme.surface;

return Container(
decoration: BoxDecoration(
borderRadius: borderRadius ?? BorderRadius.circular(16),
gradient: enableGradient
? LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
surfaceColor,
surfaceColor.withOpacity(0.95),
surfaceColor.withOpacity(0.9),
],
)
: null,
boxShadow: [
if (elevation != null && elevation! > 0)
BoxShadow(
color: colorScheme.shadow.withOpacity(0.1),
blurRadius: elevation! * 2,
offset: Offset(0, elevation!),
),
],
),
child: Container(
padding: padding,
decoration: BoxDecoration(
borderRadius: borderRadius ?? BorderRadius.circular(16),
color: enableGradient ? null : surfaceColor.withOpacity(0.9),
),
child: child,
),
);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ExpressiveSurface widget declares enableNoise and blendMode properties, but they are not used anywhere in the build method. This suggests the implementation is incomplete. These properties should either be implemented to provide the described functionality or removed to avoid confusion.

Comment on lines +1 to +289
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';

/// Utility class for Material Design Expressive motion and transitions
class ExpressiveMotion {
// Standard Material 3 motion durations
static const Duration durationShort = Duration(milliseconds: 150);
static const Duration durationMedium = Duration(milliseconds: 250);
static const Duration durationLong = Duration(milliseconds: 350);
static const Duration durationExtraLong = Duration(milliseconds: 500);

// Standard Material 3 motion curves
static const Curve standardDecelerate = Curves.decelerate;
static const Curve standardAccelerate = Curves.easeIn;
static const Curve emphasizedDecelerate = Curves.easeOutCubic;
static const Curve emphasizedAccelerate = Curves.easeInCubic;
static const Curve standard = Curves.easeInOut;

/// Expressive fade transition with scale
static Widget fadeScaleTransition({
required Widget child,
required Animation<double> animation,
double scaleStart = 0.9,
double scaleEnd = 1.0,
}) {
return ScaleTransition(
scale: Tween<double>(begin: scaleStart, end: scaleEnd).animate(
CurvedAnimation(parent: animation, curve: emphasizedDecelerate),
),
child: FadeTransition(opacity: animation, child: child),
);
}

/// Expressive slide transition with fade
static Widget slideFadeTransition({
required Widget child,
required Animation<double> animation,
Offset slideStart = const Offset(0.0, 0.1),
}) {
return SlideTransition(
position: Tween<Offset>(begin: slideStart, end: Offset.zero).animate(
CurvedAnimation(parent: animation, curve: emphasizedDecelerate),
),
child: FadeTransition(opacity: animation, child: child),
);
}

/// Expressive shared axis transition
static Widget sharedAxisTransition({
required Widget child,
required Animation<double> animation,
SharedAxisTransitionType type = SharedAxisTransitionType.horizontal,
}) {
return SharedAxisTransition(
animation: animation,
secondaryAnimation: const AlwaysStoppedAnimation(0.0),
transitionType: type,
child: child,
);
}

/// Expressive container transform
static Widget containerTransform({
required Widget child,
required Animation<double> animation,
}) {
return SharedAxisTransition(
animation: animation,
secondaryAnimation: const AlwaysStoppedAnimation(0.0),
transitionType: SharedAxisTransitionType.scaled,
child: child,
);
}

/// Expressive button press animation
static Widget expressiveButton({
required Widget child,
required VoidCallback onPressed,
bool enabled = true,
}) {
return TweenAnimationBuilder<double>(
duration: durationShort,
tween: Tween<double>(begin: 1.0, end: 1.0),
builder: (context, scale, child) {
return Transform.scale(scale: scale, child: child);
},
child: GestureDetector(
onTapDown: enabled ? (_) => {} : null,
onTapUp: enabled ? (_) => onPressed() : null,
onTapCancel: enabled ? () {} : null,
child: AnimatedScale(
scale: enabled ? 1.0 : 0.95,
duration: durationShort,
curve: standardDecelerate,
child: child,
),
),
);
}

/// Expressive card hover effect
static Widget expressiveCard({
required Widget child,
bool isHovered = false,
VoidCallback? onTap,
}) {
return AnimatedContainer(
duration: durationMedium,
curve: standardDecelerate,
transform: Matrix4.identity()
..translate(0.0, isHovered ? -4.0 : 0.0)
..scale(isHovered ? 1.02 : 1.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
boxShadow: isHovered
? [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 8),
),
]
: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
splashFactory: InkRipple.splashFactory,
child: child,
),
),
);
}

/// Expressive list item animation
static Widget animatedListItem({
required Widget child,
required int index,
int totalItems = 1,
}) {
return TweenAnimationBuilder<double>(
duration: durationMedium,
tween: Tween<double>(begin: 0.0, end: 1.0),
curve: emphasizedDecelerate,
builder: (context, value, child) {
return Transform.translate(
offset: Offset(0.0, 50 * (1 - value)),
child: Opacity(opacity: value, child: child),
);
},
child: child,
);
}

/// Expressive shimmer effect
static Widget shimmerEffect({required Widget child, bool isLoading = false}) {
if (!isLoading) return child;

return ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
colors: [
Colors.transparent,
Colors.white.withOpacity(0.3),
Colors.transparent,
],
stops: const [0.0, 0.5, 1.0],
begin: const Alignment(-1.0, 0.0),
end: const Alignment(1.0, 0.0),
).createShader(bounds);
},
child: child,
);
}

/// Expressive page route with custom transition
static PageRouteBuilder<T> expressivePageRoute<T>({
required WidgetBuilder builder,
Duration transitionDuration = durationMedium,
RouteSettings? settings,
}) {
return PageRouteBuilder<T>(
settings: settings,
transitionDuration: transitionDuration,
pageBuilder: (context, animation, secondaryAnimation) => builder(context),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position:
Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(
CurvedAnimation(parent: animation, curve: emphasizedDecelerate),
),
child: FadeTransition(opacity: animation, child: child),
);
},
);
}

/// Expressive dialog transition
static Widget expressiveDialog({
required Widget child,
required BuildContext context,
}) {
return Dialog(
backgroundColor: Colors.transparent,
child: TweenAnimationBuilder<double>(
duration: durationMedium,
tween: Tween<double>(begin: 0.0, end: 1.0),
curve: emphasizedDecelerate,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: FadeTransition(
opacity: AlwaysStoppedAnimation(value),
child: child,
),
);
},
child: child,
),
);
}
}

/// Custom expressive page route for better navigation
class ExpressivePageRoute<T> extends PageRoute<T> {
ExpressivePageRoute({
required this.builder,
this.transitionDuration = ExpressiveMotion.durationMedium,
this.opaque = true,
this.barrierDismissible = false,
this.barrierColor,
this.barrierLabel,
this.maintainState = true,
super.settings,
});

final WidgetBuilder builder;

@override
final Duration transitionDuration;

@override
final bool opaque;

@override
final bool barrierDismissible;

@override
final Color? barrierColor;

@override
final String? barrierLabel;

@override
final bool maintainState;

@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return builder(context);
}

@override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return ExpressiveMotion.slideFadeTransition(
animation: animation,
child: child,
);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This pull request introduces the ExpressiveMotion utility class, which centralizes animation durations and curves. This is a great practice for consistency. However, the same PR also modifies app_button.dart and cached_app_icon.dart to remove dependencies on this utility class and use hardcoded Duration and Curve values instead. This is contradictory. To improve maintainability, please decide on a single approach: either consistently use the ExpressiveMotion utility across all new and refactored components, or remove it if the plan is to use inline values. Using both approaches creates confusion.

@omeritzics omeritzics deleted the cherry-pick-safe-commits branch February 14, 2026 19:08
@omeritzics omeritzics restored the cherry-pick-safe-commits branch February 14, 2026 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant